package top.milkbox.common.utils;

import lombok.extern.slf4j.Slf4j;
import top.milkbox.common.utils.base.ThrowsExceptionFunction;

import java.util.concurrent.TimeUnit;

/**
 * <h4>重试工具类</h4>
 *
 * @author milkbox
 */
@Slf4j
public class RetryUtil {

    public static final int DEFAULT_TIMES = 2; // 默认重试次数
    public static final long DEFAULT_WAIT_TIME = 3000; // 默认重试间隔时间（毫秒）

    /**
     * <h4>重试函数</h4>
     * <p>
     * 使用函数式接口方式，指定要重试的函数，当函数出现Exception类型的异常时，会进行重试。重试次数由times指定，每次重试之间会等待waitTime毫秒
     * </p>
     * <p>
     * 重试次数不包括第1次执行，所以times=2表示重试2次，共执行3次。无论如何，第1次一定会执行
     * </p>
     * <p>
     * 例：
     * </p>
     * <pre>
     * // 尝试通过接口获取token，如果失败，则再重试3次，每次重试之间等待2秒
     * String token = RetryUtil.retries((i) -> rpcGetToken(config), 3, 2000);
     * </pre>
     *
     * @param retriesFunction 需要重试的函数，此函数的参数为当前重试的次数（0表示首次），返回值会被原样返回
     * @param times           重试次数，无论传递什么值，retriesFunction一定会被执行一次
     * @param waitTime        每次重试之间的等待时间，单位为毫秒，如果小于等于0，则立即重试不进行等待
     * @param <R>             retriesFunction的返回值类型
     * @return 原样返回retriesFunction的返回值
     * @throws Exception 如果重试次数耗尽，则抛出原异常（就是retriesFunction内部的异常）
     */
    private static <R> R retries(ThrowsExceptionFunction<Integer, R> retriesFunction, int times, long waitTime)
            throws Exception {
        int i = 0;
        while (true) {
            try {
                // 执行函数，如果没有异常，则直接返回
                return retriesFunction.apply(i);
            } catch (Exception e) {
                if (i >= times) {
                    log.error("重试次数耗尽，相应的操作失败！");
                    throw e; // 抛出原异常，并结束循环
                }
                i++;
                log.warn(e.getMessage(), e);
                if (waitTime > 0) {
                    // 保留两位小数
                    log.warn(String.format("%.2f秒后重试......", (float) (waitTime) / 1000));
                    // 本质上还是Thread.sleep(waitTime)，只不过不需要处理异常，且当时间小于等于0的时候不会执行休眠操作
                    TimeUnit.MILLISECONDS.sleep(waitTime);
                }
                log.warn("正在重试（{}/{}）......", i, times);
            }
        }
    }

    /**
     * <h4>重试函数</h4>
     * {@link RetryUtil#retries(ThrowsExceptionFunction, int, long)}的重载，自定义重试次数，重试间隔为{@link RetryUtil#DEFAULT_WAIT_TIME}<br />
     * 详情参见{@link RetryUtil#retries(ThrowsExceptionFunction, int, long)}
     */
    public static <R> R retries(ThrowsExceptionFunction<Integer, R> consumer, int times) throws Exception {
        return retries(consumer, times, DEFAULT_WAIT_TIME);
    }

    /**
     * <h4>重试函数</h4>
     * {@link RetryUtil#retries(ThrowsExceptionFunction, int, long)}的重载，默认重试次数为{@link RetryUtil#DEFAULT_TIMES}，重试间隔为{@link RetryUtil#DEFAULT_WAIT_TIME}<br />
     * 详情参见{@link RetryUtil#retries(ThrowsExceptionFunction, int, long)}
     */
    public static <R> R retries(ThrowsExceptionFunction<Integer, R> consumer) throws Exception {
        return retries(consumer, DEFAULT_TIMES, DEFAULT_WAIT_TIME);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            log.info("第" + (i + 1) + "次测试开始......");
            test();
            log.info("\n\n");
        }
    }

    /**
     * 模拟除0异常测试函数
     */
    private static void test() {
        try {
            int result = RetryUtil.retries((i) -> {
                int randomInt = (int) (Math.random() * 2); // 生成0或1，模拟除0异常
                int num = 10;
                log.info("尝试计算" + num + "除以" + randomInt + "的结果......");
                return num / randomInt;
            }, 2, 1000);
            log.info("最终结果：" + result);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

}
